#pragma once
#include "../windows.h"

namespace acme {
  namespace scratch {
    using namespace acme::windows;

    class Socket
    {

      class Result
      {
        int result;
      public:
        Result(int code):result(code){
          if(code==SOCKET_ERROR)
            throw Error();
        }

        Result& operator=(int code){
          result=code;
          if(code==SOCKET_ERROR)
            throw Error();
        }
        operator int(){return result;}
      };

    public:
      union {
        SOCKET s;
        SOCKET fd;
      };
      class Address
      {
        friend class Socket;
        union {
          sockaddr addr;
          sockaddr_in addr_in;
        };
        int cb;
      public:
        Address()
        {
          sockaddr_in addr = {0};
          addr_in = addr; // invoke struct copy
          cb=sizeof addr;
        }
        Address(char* name,bool isHostname,uint16_t port)
        {
          sockaddr_in addr = {0};
          addr.sin_port = ntohs(port);
          if(isHostname)
          {
            hostent* host = gethostbyname(name);
            addr.sin_family = host->h_addrtype;
            int len = ntohl(host->h_length);
            if( len>14)
              throw Error(WSAEBADF);
            memcpy(&addr.sin_addr,host->h_addr_list[0],len);
          }
          else
          {
            addr.sin_family = AF_INET;
            addr.sin_addr.S_un.S_addr = inet_addr(name);
          }
          addr_in = addr; // invoke struct copy
          cb=sizeof addr_in;
        }
        Address(uint32_t ipaddr,uint16_t port=0)
        {
          addr_in.sin_port = ntohs(port);
          addr_in.sin_family = AF_INET;
          addr_in.sin_addr.S_un.S_addr = htonl(ipaddr);
          cb=sizeof addr_in;
        }
        const char* ToCStr()
        {
          in_addr in;
          in.S_un.S_addr = addr_in.sin_addr.S_un.S_addr;
          return inet_ntoa(in);
        }
      };

      class Initializer
      {
        WSADATA wd;
      public:
        Initializer()
        {
          Result(WSAStartup(0x0101,&wd));
        }
        ~Initializer()
        {
          Result((int)WSACleanup());
        }
      };

    public:
      Socket(int af,int type,int protocol)
      {
        s = socket(af,type,protocol);
        if(s==INVALID_SOCKET)
          throw Error();
      }
      ~Socket()
      {
        if(s!=INVALID_SOCKET)
        {
          Result(closesocket(s));
        }
      }
      void Bind(Address* addr)
      {
        Result(bind(s,&addr->addr,addr->cb));
      }
      void Bind(u_short port)
      {
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons(port);
        Result(bind(s,(sockaddr*)&addr,sizeof(sockaddr_in)));
      }
      void SendTo(const void* buf,int len,Address* d)
      {
        Result(sendto(s,(const char*)buf, len,0,&d->addr,d->cb));
      }
      int RecvFrom(void* buf,int len,Address* d)
      {
        return recvfrom(s,(char*)buf,len,0,&d->addr,&d->cb);
      }
      void SetSockOpt(int level,int optname,const void*optval,int optlen)
      {
        Result(setsockopt(s,level,optname,(const char*)optval,optlen));
      }
      void SetSockOpt(int level,int optname,int opt)
      {
        Result(setsockopt(s,level,optname,(const char*)&opt,sizeof opt));
      }
    };

  }
}